#include <Ice/Application.h>
#include <IceUtil/Thread.h>
#include <IceUtil/Monitor.h>
#include <IceUtil/Time.h>
#include <IceGrid/Registry.h>
#include <Ripper.h>
#include <sndfile.h>

using namespace std;
using namespace Ice;
using namespace Ripper;

class SessionRefreshThread:
  public IceUtil::Thread,
  public IceUtil::Monitor<IceUtil::Mutex>
{
public:

  SessionRefreshThread(const IceUtil::Time& timeout,
		       const IceGrid::SessionPrx& session):
    _timeout(timeout),
    _session(session),
    _destroyed(false)
  {
  }

  virtual void run()
  {
    Lock sync(*this);
    while (!_destroyed)
      {
	timedWait(_timeout);
	try
	  {
	    _session->keepAlive();
	  }
	catch (const Ice::Exception& ex)
	  {
	    cerr << "SessionRefreshThread: " << ex << endl;
	    _destroyed = true;
	  }
      }
  }

  void destroy()
  {
    Lock sync(*this);
    _destroyed = true;
    notify();
  }

private:
  const IceUtil::Time _timeout;
  const IceGrid::SessionPrx _session;
  bool _destroyed;
};

typedef IceUtil::Handle<SessionRefreshThread> SessionRefreshThreadPtr;

class Client:
  virtual public Ice::Application
{
public:
  virtual int run(int, char*[]);
  void encode(const Mp3EncoderFactoryPrx& factory, const string& file);
};

int
Client::run(int argc,
	    char* argv[])
{
  IceGrid::RegistryPrx registry = IceGrid::RegistryPrx::
    checkedCast(communicator()->stringToProxy("EncoderIceGrid/Registry"));
  IceGrid::SessionPrx session;

  try
    {
      session = registry->createSession("foo", "bar");
    }
  catch (const IceGrid::PermissionDeniedException& ex)
    {
      cout << "permission denied:\n" << ex.reason << endl;
      return EXIT_FAILURE;
    }

  SessionRefreshThreadPtr refresh =
    new SessionRefreshThread(IceUtil::Time::seconds(registry->getSessionTimeout() / 2),
						    session);
  refresh->start();

  ObjectPrx obj =
    session->allocateObjectByType(Mp3EncoderFactory::ice_staticId());
  try
    {
      Mp3EncoderFactoryPrx factory =
	Mp3EncoderFactoryPrx::checkedCast(obj);
      for (int i = 1; i < argc; ++i)
	{
	  encode(factory, argv[1]);
	}
    }
  catch (const Exception& ex)
    {
      cerr << "Exception: " << ex << endl;
    }

  session->releaseObject(obj->ice_getIdentity());

  refresh->destroy();
  refresh->getThreadControl().join();

  session->destroy();
}

void
Client::encode(const Mp3EncoderFactoryPrx& factory,
	       const string& file)
{
  SF_INFO sfinfo;
  memset(&sfinfo, 0, sizeof(sfinfo));
  SNDFILE* sf = sf_open(file.c_str(), SFM_READ, &sfinfo);
  if(sf == 0)
    {
      cerr << "cannot open: " << file << endl;
      return;
    }

  FILE* outf = fopen((file + ".mp3").c_str(), "w");
  if(outf == 0)
    {
      cerr << "cannot open: " << file + ".mp3" << endl;
      sf_close(sf);
      return;
    }

  Mp3EncoderPrx encoder =
    factory->createEncoder(sfinfo.channels, sfinfo.samplerate);

  try
    {
      int nsamples = (1000 * 1000) / 8;
      Ice::ShortSeq lbuf(nsamples), rbuf(nsamples);
      Ice::ShortSeq sbuf(nsamples*sfinfo.channels);
      ByteSeq bytes;
      for(;;)
	{
	  sf_count_t rn = sf_read_short(sf, &sbuf[0], sbuf.size());
	  assert(rn <= 2147483647L);
	  int n = (int)rn;
	  if(n == 0)
	    {
	      break;
	    }
	  // Copy the samples into the lbuf and rbuf respectively.
	  if(sfinfo.channels == 2)
	    {
	      int samples = n / 2;
	      rbuf.resize(samples);
	      lbuf.resize(samples);
	      for(int i = samples; --i >= 0; )
		{
		  lbuf[i] = sbuf[--n];
		  rbuf[i] = sbuf[--n];
		}
	    }
	  else // channels = 1
	    {
	      int samples = n;
	      rbuf.resize(0);
	      lbuf.resize(samples);
	      for(int i = samples; --i >= 0; )
		{
		  lbuf[i] = sbuf[--n];
		}
	    }
	  
	  bytes = encoder->encode(lbuf, rbuf);
	  if (fwrite(&bytes[0], 1, bytes.size(), outf) != bytes.size())
	    {
	      throw EncodingFailedException("error writing mp3 output");
	    }
	}
      
      bytes = encoder->flush();
      
      if (fwrite(&bytes[0], 1, bytes.size(), outf) != bytes.size())
	{
	  throw EncodingFailedException("error writing mp3 output");
	}
    }
  catch(const Exception& e)
    {
      try
	{
	  encoder->flush();
	}
      catch(const Exception&)
	{
	}
      if(sf != 0)
	{
	  sf_close(sf);
	}
      if(outf != 0)
	{
	  fclose(outf);
	}
      throw;
    }
  
  if(sf != 0)
    {
      sf_close(sf);
    }
  if(outf != 0)
    {
      fclose(outf);
    }
}

int
main(int argc,
     char* argv[])
{
  Client* client = new Client;
  return client->main(argc, argv, "client.config");
}
